#include <blackbox/blackbox.h>

#ifdef BUOLA_HAVE_PULSE

#include "cpulsemixer.h"
#include "cpulseloop.h"
#include "cpulsecontext.h"
#include <pulse/subscribe.h>

namespace buola { namespace media { namespace pulse {

void sink_info_cb(pa_context *pCtx,const pa_sink_info *pI,int pEOL,void *pMixer)
{
    //the pulse audio loop lock is held when this function is called
    CPulseMixer *lMixer=reinterpret_cast<CPulseMixer*>(pMixer);
    
    if(pEOL)
    {
        lMixer->mHaveSinkInfo=true;
        lMixer->mContext.GetLoop().Signal();
        return;
    }
    
    CPulseMixer::SSink lSink;
    lSink.mID=pI->index;
    lSink.mName=utf32(pI->description);
    lSink.mVolume=pI->volume;
    lSink.mMute=pI->mute;
    lSink.mNumChannels=pI->channel_map.channels;
    lMixer->mSinks[lSink.mID]=lSink;
    lMixer->mElements.push_back(lSink.mID);
}

void sink_info_update_cb(pa_context *pCtx,const pa_sink_info *pI,int pEOL,void *pMixer)
{
    //the pulse audio loop lock is held when this function is called
    CPulseMixer *lMixer=reinterpret_cast<CPulseMixer*>(pMixer);
    
    if(pEOL)
        return;

    auto i=lMixer->mSinks.find(pI->index);
    if(i!=lMixer->mSinks.end())
    {
        i->second.mVolume=pI->volume;
        i->second.mMute=pI->mute;
    }
    lMixer->eChange(pI->index);
}

void subscribe_cb(pa_context *pCtx,const pa_subscription_event_type pEvent,uint32_t pIndex,void *pMixer)
{
    //the pulse audio loop lock is held when this function is called
    CPulseMixer *lMixer=reinterpret_cast<CPulseMixer*>(pMixer);
    
    if((pEvent&PA_SUBSCRIPTION_EVENT_FACILITY_MASK)==PA_SUBSCRIPTION_EVENT_SINK&&
       (pEvent&PA_SUBSCRIPTION_EVENT_TYPE_MASK)==PA_SUBSCRIPTION_EVENT_CHANGE)
    {
        auto i=lMixer->mSinks.find(pIndex);
        if(i!=lMixer->mSinks.end())
        {
            pa_context_get_sink_info_by_index(pCtx,pIndex,sink_info_update_cb,pMixer);
        }
    }
}

void success_cb(pa_context *pCtx,int pSuccess,void *pMixer)
{
}

CPulseMixer::CPulseMixer(CPulseContext &pContext)
    :   mContext(pContext)
    ,   mHaveSinkInfo(false)
{
    CPulseLock lLock(mContext.GetLoop());
    pa_context_get_sink_info_list(mContext.mCtx,sink_info_cb,this);
    while(!mHaveSinkInfo)
        lLock.Wait();
    pa_context_set_subscribe_callback(mContext.mCtx,subscribe_cb,this);
    pa_context_subscribe(mContext.mCtx,PA_SUBSCRIPTION_MASK_SINK,success_cb,this);
}

CMixer::TElement CPulseMixer::GetElement(const std::wstring &pName)
{
    for(auto i=mSinks.begin();i!=mSinks.end();i++)
    {
        if(i->second.mName==pName)
            return i->first;
    }
    
    throw XNotFound("mixer element not found");
}

double CPulseMixer::GetVolume(TElement pElement,int pChannel)
{
    SSink &lSink=mSinks[pElement];
    return double(pa_cvolume_max(&lSink.mVolume))/PA_VOLUME_NORM;
}

void CPulseMixer::SetVolume(TElement pElement,double pValue,int pChannel)
{
    SSink &lSink=mSinks[pElement];
    pa_cvolume_set(&lSink.mVolume,lSink.mNumChannels,pValue*PA_VOLUME_NORM);
    CPulseLock lLock(mContext.GetLoop());
    pa_context_set_sink_volume_by_index(mContext.mCtx,lSink.mID,&lSink.mVolume,success_cb,this);
}

bool CPulseMixer::GetMute(TElement pElement,int pChannel)
{
    SSink &lSink=mSinks[pElement];
    return lSink.mMute;
}

void CPulseMixer::SetMute(TElement pElement,bool pValue,int pChannel)
{
    SSink &lSink=mSinks[pElement];
    pa_context_set_sink_mute_by_index(mContext.mCtx,lSink.mID,pValue,success_cb,this);
}

/*namespace pulse*/ }

CMixer &get_simple_mixer()
{
    static pulse::CPulseMixer lMixer;
    return lMixer;
}

/*namespace media*/ } /*namespace buola*/ }

#endif
